/** 
 * @file radclient.c
 * @brief this is the radclient for performance test
 * @author Red Li
 * @version 1.0.0
 * @date 2009-08-15
 *
 */


#include "autoconf.h"

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#ifdef HAVE_UNISTD_H
#	include <unistd.h>
#endif

#include <string.h>
#include <strings.h>
#include <ctype.h>
#include <netdb.h>
#include <sys/socket.h>

#ifdef HAVE_NETINET_IN_H
#	include <netinet/in.h>
#endif

#ifdef HAVE_SYS_SELECT_H
#	include <sys/select.h>
#endif

#ifdef HAVE_GETOPT_H
#	include <getopt.h>
#endif

#include <assert.h>
#include <pthread.h>
#include <signal.h>

#include "conf.h"
#include "radpaths.h"
#include "missing.h"
#include "libradius.h"
#include "utils.h"
#include "radclient.h"
#include "server.h"
#include "output.h"



//!< each package should be send <retries> after timeout
static int retries = 3;
//!< resend 
static int resend = 1;
//!< timeout (ms)
utime_t time_out = 300 * (TIME_GAP / 1000);							
//!< server ipaddr
static uint32_t server_ipaddr = 0;
//
//!< request tree 
static pthread_mutex_t request_tree_mutex;
static rbtree_t *request_tree = NULL;
//!< session control, 0 turn off, 1 turn on 
static int session_control = 0;
//
//
extern float reject_per;
extern float drop_per;
//!< 
int count_per = 0x7fffffff;
int busy_pkt = 0;
int timeout_decrease = 0;
int count_decrease = 0;

utime_t statis_start;


/* --------------------------------------------------------------------------*/
/** 
 * @brief this structure is the base class of double circle list
 * 
 * @review draft
 */
/* ----------------------------------------------------------------------------*/
typedef struct list_t
{
	void			*next;
	void			*prev;
}list_t;


/* --------------------------------------------------------------------------*/
/** 
 * @brief package structure
 * 
 * @review draft
 */
/* ----------------------------------------------------------------------------*/
typedef struct package_t
{
	struct package_t	*next;
	struct package_t	*prev;

	RADIUS_PACKET		*request;
	RADIUS_PACKET		*reply;
	int					retries;
	utime_t				timestamp;
	const char			*filename;
	char				password[256];
	int					packet_number;
}package_t;


pthread_mutex_t	state_mutex;
extern int state_count[CLIENT_TOTAL + 1];
#define STATE_DEC(state) \
{\
	pthread_mutex_lock(&state_mutex); \
	state_count[state] --; \
	pthread_mutex_unlock(&state_mutex); \
}
#define STATE_INC(state) \
{\
	pthread_mutex_lock(&state_mutex); \
	state_count[state] ++; \
	pthread_mutex_unlock(&state_mutex); \
}
extern unsigned int statis[CLASS_END][TYPE_END];

/* --------------------------------------------------------------------------*/
/** 
 * @brief structure for each radclient
 * 
 * @review draft
 */
/* ----------------------------------------------------------------------------*/
typedef struct radclient_t {
	struct				radclient_t *next;
	struct				radclient_t *prev;

	package_t			*head;					//!< header of package list
	package_t			*cur;					//!< pos indicate the current packet should to send
	client_state_t		state;					//!< 
	int					resend;
#ifdef REJECT_RESENT
	int					reject_flag;			//!<this is used for statis trans retries beause of reject message
#endif

} radclient_t;



//!< resources of sock with radius id[0-255]
pthread_mutex_t resource_mutex;
extern resources_t *resource;		
//!< size of resource
extern int num_resource;




//!< clients list head
static radclient_t *radclient_head = NULL;
//!< position for send pos, this position is IDLE always
static pthread_mutex_t send_pos_mutex;
static radclient_t *send_pos = NULL;
//!< timout position
static pthread_mutex_t timeout_pos_mutex;
static radclient_t *timeout_pos = NULL;

#define CLEAR_PACKET(pkt) \
{\
	pthread_mutex_lock(&resource_mutex); \
	free_radius_id((pkt)->request->sockfd, (pkt)->request->id); \
	pthread_mutex_unlock(&resource_mutex); \
	(pkt)->request->id = -1;\
	(pkt)->timestamp = -1; \
	(pkt)->request->sockfd = 0; \
	(pkt)->retries = 0; \
	if((pkt)->request->data) \
	{\
		free((pkt)->request->data);\
		(pkt)->request->data = 0; \
		(pkt)->request->data_len = 0; \
	} \
}

/* --------------------------------------------------------------------------*/
/** 
 * @brief print out the help message and exit
 * 
 * @return 
 * 
 * @review draft
 */
/* ----------------------------------------------------------------------------*/
static void NEVER_RETURNS usage(void)
{
	printf("\t    -t timeout (ms) \n \
            -n count_per \n \
            -r retries \n \
            -c resend\n \
            -b busy_pkt \n \
            -j reject_per \n \
            -p drop_per\n \
            -t timeout\n \
            -w (no arg)session control\n \
            -k timeout_decrease \n \
            -d count_decrease \n");

    exit(1);
}

static void dump_request(package_t *pkt, FILE *fp)
{
	char ip_buffer[128];
	VALUE_PAIR *vp;
	assert(pkt && fp);

	if(pkt->retries > 1)
		fprintf(fp, "Re-sending %s of id %d to %s port %d\n", 
				packet_codes[pkt->request->code], pkt->request->id,
				ip_ntoa(ip_buffer, pkt->request->dst_ipaddr),
				pkt->request->dst_port);
	else
		fprintf(fp, "Sending %s of id %d to %s port %d\n", 
				packet_codes[pkt->request->code], pkt->request->id,
				ip_ntoa(ip_buffer, pkt->request->dst_ipaddr),
				pkt->request->dst_port);

	for (vp = pkt->request->vps; vp; vp = vp->next)
	{
		putchar('\t');
		vp_print(fp, vp);
		putchar('\n');
	}

}

static void dump_reply(package_t *pkt, FILE *fp)
{
	char ip_buffer[128];
	VALUE_PAIR *vp;
	assert(pkt && fp);



	fprintf(fp, "recv %s of id %d from %s port %d\n", 
				packet_codes[pkt->reply->code], pkt->reply->id,
				ip_ntoa(ip_buffer, pkt->reply->src_ipaddr),
				pkt->reply->src_port);

	for (vp = pkt->reply->vps; vp; vp = vp->next)
	{
		putchar('\t');
		vp_print(fp, vp);
		putchar('\n');
	}

}

/* --------------------------------------------------------------------------*/
/** 
 * @brief statis the recv packet
 * 
 * @param radclient client which receive the packet
 * @param flag	some extra information, if flag > 0, indicate that this packet have some errors
 * 
 * @review draft
 */
/* ----------------------------------------------------------------------------*/
static void recv_statis(radclient_t *radclient, int flag)
{
	package_t *pkt;
#define RECV_STATIS_QT(prefix, type) \
	{\
		statis[prefix ##QUERY][type]++;\
		if(pkt->retries >= retries && (type) == DETECTLOST) \
			statis[prefix ##TRANS][type]++; \
		if((type) == COMPLETED)		\
			statis[prefix ##TRANS][type]++; \
	}

	assert(radclient &&  radclient->cur && radclient->cur->reply);
	
	pkt = radclient->cur;
	rad_decode(pkt->reply, NULL, secret);
	//dump_reply(pkt, stdout);
	//!< error packet
	if(flag)
	{ 
		//!<query and trans lost statis
		RECV_STATIS_QT(, DETECTLOST);

		//!<session statis
		if(pkt->retries >= retries)
			statis[SESSION][DETECTLOST]++;

		switch(pkt->request->code)
		{
		case PW_AUTHENTICATION_REQUEST:
			RECV_STATIS_QT(AUTH, DETECTLOST);
			break;

		case PW_ACCOUNTING_REQUEST:
			RECV_STATIS_QT(ACCT, DETECTLOST);

			switch(pkt->request->status_code)
			{
				case PW_STATUS_START:
					break;
				case PW_STATUS_ALIVE:
					break;
				case PW_STATUS_STOP:
					break;
				default:
					ERROR("unknown status code:%d", pkt->request->status_code);
					break;
			}
			break;
		default:
			DBG("error\n");
			break;
		}
	}
	else   //!< recv a correct reply
	{
		//!<query and trans completed statis
		RECV_STATIS_QT(, COMPLETED);

		//!<session statis
		switch(pkt->request->code)
		{
		case PW_AUTHENTICATION_REQUEST:
			RECV_STATIS_QT(AUTH, COMPLETED);
			
			switch(pkt->reply->code)
			{
			case PW_AUTHENTICATION_ACK:			//!< AUTHENTICATION SUCCESS
				statis[AUTHQUERY][ACCEPTED]++;
				break;

			case PW_AUTHENTICATION_REJECT:
				statis[AUTHQUERY][REJECT]++;
#ifdef REJECT_RESENT
				if(pkt->retries >= retries)
					statis[SESSION][REJECT]++;
#else
					statis[SESSION][REJECT]++;
#endif
				break;

			default:
				ERROR("error reply(code:%d) for request\n", pkt->reply->code);
				break;

			}
			break;

		case PW_ACCOUNTING_REQUEST:
			RECV_STATIS_QT(ACCT, COMPLETED);
			
			switch(pkt->request->status_code)
			{
				case PW_STATUS_START:
					break;

				case PW_STATUS_ALIVE:
					break;

				case PW_STATUS_STOP:
					assert(pkt->next == radclient->head);
					statis[SESSION][COMPLETED]++;
					break;

				default:
					ERROR("unknown status code:%d", pkt->request->status_code);
					break;
			}

			break;

		default:
			DBG("error\n");
			break;
		}

	}
}

/* --------------------------------------------------------------------------*/
/** 
 * @brief send packet statis
 *  
 * @param radclient client which will send a packet
 * @param flag
 * 
 * @review draft
 */
/* ----------------------------------------------------------------------------*/
static void send_statis(radclient_t *radclient, int flag)
{
	package_t *pkt;
	assert(radclient && radclient->cur);

	pkt = radclient->cur;

#ifndef REJECT_RESENT
#define SENT_STATIS_QT(prefix) \
	{					\
		statis[prefix ##QUERY][SENT]++; \
		if(pkt->retries > 1)		\
		{\
			statis[prefix ##QUERY][RESENT]++; \
		} \
		else \
		{\
			statis[prefix ##TRANS][SENT]++; \
		}\
	} 
#else
#define SENT_STATIS_QT(prefix) \
	{					\
		statis[prefix ##QUERY][SENT]++; \
		if(pkt->retries > 1)		\
			statis[prefix ##QUERY][RESENT]++; \
		if(pkt->retries <= 1 || radclient->reject_flag) \
		{\
			radclient->reject_flag = 0; \
			statis[prefix ##TRANS][SENT]++; \
		}\
	}
#endif

	//!< query and transation statis
	SENT_STATIS_QT();
	//
	//!< session statis
	if(pkt == radclient->head && pkt->retries <= 1)
		statis[SESSION][SENT]++;

	//dump_request(pkt, stdout);

	switch(pkt->request->code)
	{
		case PW_AUTHENTICATION_REQUEST:
			SENT_STATIS_QT(AUTH);
			break;

		case PW_ACCOUNTING_REQUEST:
			SENT_STATIS_QT(ACCT);

			switch(pkt->request->status_code)
			{
				case PW_STATUS_START:
					break;
				case PW_STATUS_ALIVE:
					break;
				case PW_STATUS_STOP:
					break;
				default:
					ERROR("unknown status code:%d", pkt->request->status_code);
					break;
			}
			break;
		default:
			break;
	}
	

}



/* --------------------------------------------------------------------------*/
/** 
 * @brief timeout packet statis
 * 
 * @param radclient client which package is timeout
 * @param flag some extra information, not used here
 * 
 * @review draft
 */
/* ----------------------------------------------------------------------------*/
static void timeout_statis(radclient_t *radclient, int flag)
{
	package_t *pkt;
#define TIMEOUT_STATIS_QT(prefix, type) \
	{\
		statis[prefix ##QUERY][type]++;\
		if(pkt->retries >= retries) \
			statis[prefix ##TRANS][type]++; \
	}

	assert(radclient && 
			radclient->cur);

	pkt = radclient->cur;
	
	//!<query and trans lost statis
	TIMEOUT_STATIS_QT(, TIMEOUT);

	//!<session statis
	if(pkt->retries >= retries)
		statis[SESSION][TIMEOUT]++;

	switch(pkt->request->code)
	{
	case PW_AUTHENTICATION_REQUEST:
		TIMEOUT_STATIS_QT(AUTH, TIMEOUT);
		break;

	case PW_ACCOUNTING_REQUEST:
		TIMEOUT_STATIS_QT(ACCT, TIMEOUT);

		switch(pkt->request->status_code)
		{
			case PW_STATUS_START:
				break;
			case PW_STATUS_ALIVE:
				break;
			case PW_STATUS_STOP:
				break;
			default:
				ERROR("unknown status code:%d", pkt->request->status_code);
				break;
		}
		break;
	default:
		DBG("error timeout\n");
		break;
	}

}

/* --------------------------------------------------------------------------*/
/** 
 * @brief combine two double circle list
 * 
 * @param l1 list1, can't be set to NULL
 * @param l2 list2, can't be set to NULL
 * 
 * @return no meaning here
 * 
 * @review draft
 */
/* ----------------------------------------------------------------------------*/
static inline int combine_list(list_t *l1, list_t *l2)
{
	list_t *t1, *t2;
	assert(l1 && l2);
	
	////DBG("conbin_list()\n");
	t1 = l1->next;
	t2 = l2->prev;

	l1->next = l2;
	l2->prev = l1;

	t2->next = t1;
	t1->prev = t2;
	return 0;
	
}

/* --------------------------------------------------------------------------*/
/** 
 * @brief remove a node from a double circle list, and current position move to next
 * 
 * @param list list node will be remove, note(type is list_t**)
 * 
 * @return the list node be removed
 * 
 * @review draft
 */
/* ----------------------------------------------------------------------------*/
static inline list_t *remove_from_list(list_t **list)
{
	list_t *prev, *next, *cur;
	assert(*list);

	//DBG("remove_from_list()\n");
	prev = (*list)->prev;
	next = (*list)->next;
	cur = *list;

	if(next == (*list))
	{
		assert((*list) == prev);
		*list = NULL;
	}
	else
	{
		assert((*list) != prev);
		prev->next = next;
		next->prev = prev;
		cur->prev = cur->next = cur;
		*list = next;
	}

	return cur;
}

/* --------------------------------------------------------------------------*/
/** 
 * @brief free a package_t structure
 * 
 * @param pkt package will be freed
 * 
 * @review draft
 */
/* ----------------------------------------------------------------------------*/
static void package_free(package_t *pkt)
{
	assert(pkt);

	//DBG("package free\n");
	if(pkt->request)
		rad_free(&pkt->request);
	if(pkt->reply)
		rad_free(&pkt->reply);

	free(pkt);

}

/* --------------------------------------------------------------------------*/
/** 
 * @brief free a radclient_t structure
 * 
 * @param data radclient will be freed
 * 
 * @review draft
 */
/* ----------------------------------------------------------------------------*/
static void client_free(void **data)
{
	radclient_t *radclient,
				*next, *prev;
	package_t *pkt;
	//DBG("client free");
	if (!(*data))
		return;

	radclient = *data;

	next = radclient->next;
	prev = radclient->prev;

	
	while(radclient->head)
	{
		pkt = (package_t*)remove_from_list((list_t**)(&radclient->head));
		package_free(pkt);
	}

	if(next == radclient) //only one node
	{
		assert(next == prev);
		free(radclient);
		data = NULL;
	}
	else
	{
		next->prev = prev;
		prev->next = next;
		free(radclient);
		*data = next;
	}

}

/* --------------------------------------------------------------------------*/
/** 
 * @brief usefulless
 * 
 * @param data
 * 
 * @review draft
 */
/* ----------------------------------------------------------------------------*/
static void radclient_free(void *data)
{
	//DBG("radclient free\n");
	return;

}


/* --------------------------------------------------------------------------*/
/** 
 * @brief initialize clients from the file<filename>
 * 
 * @param filename contained initialize data
 * 
 * @return double circle list of radclient be initialized 
 * 
 * @review draft
 */
/* ----------------------------------------------------------------------------*/
static radclient_t *radclient_init(const char *filename)
{
	FILE *fp;
	VALUE_PAIR *vp;
	radclient_t *start = NULL,
				*radclient = NULL;
	package_t 	*pkt = NULL;

	int filedone = 0;
	int packet_number = 1;

	int flag = 0;
	//!< 0	ACCT_STOP
	//!< 1  AUTH
	//!< 2  ACCT_START | ACCT_UPDATE

	//DBG("radclient init\n");
	start = NULL;
	assert(filename != NULL);

	/*
	 *	Determine where to read the VP's from.
	 */
	if (strcmp(filename, "-") != 0) {
		fp = fopen(filename, "r");
		if (!fp) {
			fprintf(stderr, "radclient: Error opening %s: %s\n",
				filename, strerror(errno));
			return NULL;
		}
	} else {
		fp = stdin;
	}

	/*
	 *	Loop until the file is done.
	 */
	do {
		/*
		 *	Allocate it.
		 */
		if(flag == 0 || session_control == 0)
		{
			if(radclient)  //!< reset for old radclient
				radclient->cur = radclient->head;

			//!< new radclient
			radclient = malloc(sizeof(*radclient));
			if (!radclient)
			{
				serr("radclient malloc error");
				goto _error;
			}
			
			radclient->next = radclient->prev = radclient;
			radclient->head = radclient->cur = NULL;
			radclient->state = CLIENT_IDLE;
#ifdef REJECT_RESENT
			radclient->reject_flag = 0;
#endif
			radclient->resend = 0;

			if (!start)
				start = radclient;
			else
				combine_list((list_t*)start->prev, (list_t*)radclient);			//!< add the new radclient to the tail

		}

		pkt = malloc(sizeof(*pkt));
		if (!pkt)
		{
			serr("package malloc error");
			goto _error;
		}
		bzero(pkt, sizeof(*pkt));

		pkt->request = rad_alloc(1);
		if (!pkt->request)
		{
			serr("request mallock error");
			goto _error;
		}

		pkt->filename = filename;
		pkt->request->id = -1; /* allocate when sending */
		pkt->packet_number = packet_number++;
		pkt->timestamp = -1;
		pkt->next = pkt->prev = pkt;


		/*
		 *	Read the VP's.
		 */
		pkt->request->vps = readvp2(fp, &filedone, "radclient: X");
		if (!pkt->request->vps)
		{
			serr("error when reading vps");
			goto _error;
		}


		/*
		 *	Keep a copy of the the User-Password attribute.
		 */
		if ((vp = pairfind(pkt->request->vps, PW_PASSWORD)) != NULL)
		{
			strNcpy(pkt->password, (char *)vp->strvalue, sizeof(pkt->password));
			/*
			 *	Otherwise keep a copy of the CHAP-Password attribute.
			 */
		} 
		else if ((vp = pairfind(pkt->request->vps, PW_CHAP_PASSWORD)) != NULL)
			strNcpy(pkt->password, (char *)vp->strvalue, sizeof(pkt->password));
		else 
			pkt->password[0] = '\0';

		/*
		 *  Fix up Digest-Attributes issues
		 */
		for (vp = pkt->request->vps; vp != NULL; vp = vp->next) {
			switch (vp->attribute) {
			default:
				break;

				/*
				 *	Allow it to set the packet type in
				 *	the attributes read from the file.
				 */
			case PW_PACKET_TYPE:
				pkt->request->code = vp->lvalue;
				if(vp->lvalue == PW_AUTHENTICATION_REQUEST)
					if(flag == 0 || !session_control)
						flag = 1;
					else
					{
						serr("error package sequence");
						goto _error;
					}
					
				break;

			
			case PW_ACCT_STATUS_TYPE:
				pkt->request->status_code = vp->lvalue;
				switch(vp->lvalue)
				{
					case PW_STATUS_START:
						if(flag == 1 || !session_control)
							flag = 2;
						else
						{
							serr("error in package sequence");
							goto _error;
						}
						break;

					case PW_STATUS_ALIVE:
						if(flag == 2 || !session_control)
							break;
						else
						{
							serr("error in package sequence");
							goto _error;
						}

					case PW_STATUS_STOP:
						if(flag == 2 || !session_control)
							flag = 0;
						else
						{
							serr("error in package sequence");
							goto _error;
						}
						break;

					default:
						serr("error status type");
						goto _error;
				}
				break;
	

			case PW_PACKET_DST_PORT:
				pkt->request->dst_port = (vp->lvalue & 0xffff);
				break;

			case PW_DIGEST_REALM:
			case PW_DIGEST_NONCE:
			case PW_DIGEST_METHOD:
			case PW_DIGEST_URI:
			case PW_DIGEST_QOP:
			case PW_DIGEST_ALGORITHM:
			case PW_DIGEST_BODY_DIGEST:
			case PW_DIGEST_CNONCE:
			case PW_DIGEST_NONCE_COUNT:
			case PW_DIGEST_USER_NAME:
				/* overlapping! */
				memmove(&vp->strvalue[2], &vp->strvalue[0], vp->length);
				vp->strvalue[0] = vp->attribute - PW_DIGEST_REALM + 1;
				vp->length += 2;
				vp->strvalue[1] = vp->length;
				vp->attribute = PW_DIGEST_ATTRIBUTES;
				break;
			}
		} /* loop over the VP's we read in */
		
		if(!radclient->cur)
			radclient->cur = pkt;
		else
			combine_list((list_t*)radclient->cur, (list_t*)pkt);
		radclient->cur = radclient->cur->next;
		radclient->head = radclient->head ? radclient->head : radclient->cur;
	} while (!filedone); /* loop until the file is done. */


	/*
	 *	And we're done.
	 */
	return start;

_error:
	if (fp != stdin)
		fclose(fp);
	
	if (pkt)
		package_free(pkt);

	if (radclient)
	{
		radclient = (radclient_t*)remove_from_list((list_t**)(&radclient));
		if(radclient == start)
			start = NULL;
		client_free((void **)&radclient);
	}
	perr(OTHER_ERROR);

	return start;

}


/* --------------------------------------------------------------------------*/
/** 
 * @brief compare radcleint's package
 * 
 * @param one client which current package will be compared
 * @param two same as one
 * 
 * @return compare result
 * 
 * @review draft
 */
/* ----------------------------------------------------------------------------*/
static int request_cmp(const void *one, const void *two)
{
	const package_t *a = ((radclient_t*)one)->cur;
	const package_t *b = ((radclient_t*)two)->cur;

	//DBG("reqeust cmp\n");
	/*
	 *	The following code looks unreasonable, but it's
	 *	the only way to make the comparisons work.
	 */
	if (a->request->id < b->request->id) return -1;
	if (a->request->id > b->request->id) return +1;

	if (a->request->dst_ipaddr < b->request->dst_ipaddr) return -1;
	if (a->request->dst_ipaddr > b->request->dst_ipaddr) return +1;

	if (a->request->dst_port < b->request->dst_port) return -1;
	if (a->request->dst_port > b->request->dst_port) return +1;

	if (a->request->sockfd < b->request->sockfd) return -1;
	if (a->request->sockfd > b->request->sockfd) return +1;

	/*
	 *	Everything's equal.  Say so.
	 */
	return 0;


	
}

/* --------------------------------------------------------------------------*/
/** 
 * @brief get a radius id from the resources
 * 
 * @param sockfd sockfd the radius id belong to
 * 
 * @return radius id
 * 
 * @review draft
 */
/* ----------------------------------------------------------------------------*/
static int get_radius_id(int *sockfd)
{
	int i, j;
	//DBG("get radius id\n");
	for(i = 0; i < num_resource; i++)
	{
		if(resource[i].num_used < 255)
		{
			for(j = resource[i].cur + 1; j != (resource[i].cur & 0xff); j = (j + 1) & 0xff)
			{
				if(resource[i].radius_id[j] == 0)
				{
					resource[i].radius_id[j] = 1;
					resource[i].num_used += 1;
					*sockfd = resource[i].sockfd;
					resource[i].cur = j;
					return j;
				}
			}
			ERROR("error when get radius id(resource[%d], cur:0x%x used:%d\n",
					i, resource[i].cur, resource[i].num_used);
		}
	}

	resource = (resources_t*)realloc(resource, sizeof(*resource) * (num_resource + 1));
	if(resource)
	{
		num_resource ++;
		j = num_resource - 1;

		bzero(resource + j, sizeof(*resource));
		if ((resource[j].sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
		{
			ERROR("radclient: socket: ");
			exit(1);
		}


		resource[j].radius_id[0] = 1;
		resource[j].cur = 0;
		resource[j].num_used = 1;
		*sockfd = resource[j].sockfd;
		return 0;
	}
	else
	{
		ERROR("resource alloc error");
		exit(0);
	}
}


/* --------------------------------------------------------------------------*/
/** 
 * @brief free a radius id belong to sockfd
 * 
 * @param sockfd sockfd the radius id belongs to
 * @param id radius id will be freed
 * 
 * @review draft
 */
/* ----------------------------------------------------------------------------*/
static void free_radius_id(int sockfd, int id)
{

	int i;
	assert(id < 256);

	//DBG("free radius id, sockfd:%d id:%d\n", sockfd, id);
	for(i = 0; i < num_resource; i++)
	{
		if(resource[i].sockfd == sockfd)
		{
			resource[i].radius_id[id] = 0;
			resource[i].num_used --;
		}
			
	}

}

static void free_resource()
{
	int i;

	//DBG("free radius id, sockfd:%d id:%d\n", sockfd, id);
	for(i = 0; i < num_resource; i++)
		close(resource[i].sockfd);
	if(resource)
		free(resource);
	resource = NULL;
	
}

/* --------------------------------------------------------------------------*/
/** 
 * @brief find a sockfd which can be read from the resource pool
 * 
 * @param timeout how long to wait
 * 
 * @return 0 timeout
 *			otherwise the sockfd can be read
 * 
 * @review draft
 */
/* ----------------------------------------------------------------------------*/
static int get_recv_sockfd(utime_t timeout)
{
	struct timeval tv;
	int ret;
	int i;
	fd_set recv_set;
	int max_sockfd = 0;
	assert(timeout >= 0);

	//DBG("get_recv_sockfd\n");
	tv.tv_sec = timeout / 1000000;
	tv.tv_usec = timeout % 1000000;

	FD_ZERO(&recv_set);

	for(i = 0; i < num_resource; i++)
	{

		max_sockfd = max_sockfd > resource[i].sockfd ? max_sockfd : resource[i].sockfd;
		FD_SET(resource[i].sockfd, &recv_set);
	}

	ret = select(max_sockfd + 1, &recv_set, NULL, NULL, &tv);

	if(ret < 1)
		return 0;

	for(i = 0; i < num_resource; i ++)
	{
		if(FD_ISSET(resource[i].sockfd, &recv_set))
			return resource[i].sockfd;
	}

	return 0;
}


/* --------------------------------------------------------------------------*/
/** 
 * @brief send a package belong to <radclient>
 * 
 * @param radclient client the package belongs to
 * 
 * @return 0 send successful
 *			
 * 
 * @review draft
 */
/* ----------------------------------------------------------------------------*/
static int send_one_packet(radclient_t *radclient)
{
	int i;
	int sockfd;
	package_t *pkt;

	assert(radclient->state == CLIENT_IDLE);
	assert(radclient->cur);
	pkt = radclient->cur;

	//DBG("send one packet\n");
	/*
	 *	Haven't sent the packet yet.  Initialize it.
	 */
	if (pkt->request->id == -1) {
		//DBG("new package\n");

		assert(pkt->reply == NULL);
	
		pthread_mutex_lock(&resource_mutex);
		pkt->request->id = get_radius_id(&sockfd);
		pthread_mutex_unlock(&resource_mutex);
		pkt->request->sockfd = sockfd;
		pkt->request->dst_ipaddr = server_ipaddr;

		assert(pkt->request->sockfd);
		assert(pkt->request->id != -1);
		assert(pkt->request->data == NULL);

		librad_md5_calc(pkt->request->vector,pkt->request->vector,
				sizeof(pkt->request->vector));

		/*
		 *	Update the password, so it can be encrypted with the
		 *	new authentication vector.
		 */
		if (pkt->password[0] != '\0')
		{
			VALUE_PAIR *vp;

			if ((vp = pairfind(pkt->request->vps, PW_PASSWORD)) != NULL)
			{
				strNcpy((char *)vp->strvalue,pkt->password, sizeof(vp->strvalue));
				vp->length = strlen(vp->strvalue);

			}
			else if ((vp = pairfind(pkt->request->vps, PW_CHAP_PASSWORD)) != NULL)
			{
				strNcpy((char *)vp->strvalue,pkt->password, sizeof(vp->strvalue));
				vp->length = strlen(vp->strvalue);

				rad_chap_encode(pkt->request, (char *) vp->strvalue,pkt->request->id, vp);
				vp->length = 17;
			}
		}

		if(pkt == radclient->head && pkt->retries == 0)		//!< this packet haven't send in this round	
			radclient->resend ++;

	}
	/*
	 *	Send the packet.
	 */
	assert(pkt->retries < retries);
	pkt->timestamp = get_time() ;
	pkt->retries += 1;

	//DBG("code:%d id:%d dst_port:%d dst_ipaddr:%d\n", pkt->request->code, pkt->request->id, pkt->request->dst_port, pkt->request->dst_ipaddr);
	if (rad_send(pkt->request, NULL, secret) < 0)
	{
		ERROR("radclient: Failed to send packet for ID %d: %s\n",
			pkt->request->id, librad_errstr);

	}
	else
	{
		pthread_mutex_lock(&request_tree_mutex);
		if (rbtree_insert(request_tree, radclient) == 0)
		{
			pthread_mutex_unlock(&request_tree_mutex);
			
			ERROR("error when insert to rbtree\n");
			assert(0 == 1);
		}
		else	//!< send successful
		{

			STATE_DEC(radclient->state);
			radclient->state = CLIENT_BUSY; //!< state maybe change immediate
			STATE_INC(radclient->state);

			pthread_mutex_lock(&send_pos_mutex);
			//send_pos = radclient->next;
			if(send_pos->state != CLIENT_IDLE)  //!< this is a fixed bug,
				send_pos = send_pos->next;
			send_statis(radclient, 0);
			pthread_mutex_unlock(&send_pos_mutex);
			pthread_mutex_unlock(&request_tree_mutex); //!< protect send_pos's content change , this is a fixed bug

		}

	}

	
	//DBG("send over\n");

	return 0;
}


/* --------------------------------------------------------------------------*/
/** 
 * @brief receive a packet, maybe 
 * 
 * @param wait_time how long to wait from the sockfd
 * 
 * @return 0 receive successful
 *			1 wati_time < 0
 *			2 timeout
 *			-1 receive a bad package or received package didn't send
 *
 * 
 * @review draft
 */
/* ----------------------------------------------------------------------------*/
static int recv_one_packet(utime_t wait_time)
{
	package_t mypkt, *pkt;
	radclient_t myclient, *radclient;
	RADIUS_PACKET	myrequest, *reply;
	rbnode_t	*node;
	int sockfd;
	int err = 0;	//!<  0 no error
					//!<  1, 2 error

	//DBG("recv one package\n");
	if(wait_time < 0)
		return 1;


	sockfd = get_recv_sockfd(wait_time);

	if (!sockfd)
		return 2;

	/*
	 *	Look for the packet.
	 */

	reply = rad_recv(sockfd);
	if (!reply) 
	{
		ERROR("radclient: received bad packet: %s\n",
			librad_errstr);
		return -1;	/* bad packet */
	}

	mypkt.request = &myrequest;
	myrequest.id = reply->id;
	myrequest.sockfd = reply->sockfd;
	myrequest.dst_ipaddr = reply->src_ipaddr;
	myrequest.dst_port = reply->src_port;
	//mypkt.next = mypkt.prev = &mypkt;
	
	//myclient.next = myclient.prev = &myclient;
	myclient.head = myclient.cur = &mypkt;
//
	//mypkt.reply = reply;
	//rad_decode(mypkt.reply, NULL, secret);
	//dump_reply(&mypkt, stdout);
	
	pthread_mutex_lock(&request_tree_mutex);
	node = rbtree_find(request_tree, &myclient);
	pthread_mutex_unlock(&request_tree_mutex);
	
	if (!node)
	{
		//ERROR("radclient: received response to request we id(%d) not send.\n", reply->id);
		rad_free(&reply);
		return -1;	/* got reply to packet we didn't send */
	}

	radclient = rbtree_node2data(request_tree, node);
	assert(radclient != NULL);
	
	assert(radclient->state == CLIENT_BUSY);

	pthread_mutex_lock(&request_tree_mutex);
	rbtree_delete(request_tree, node);
	pthread_mutex_unlock(&request_tree_mutex);
	
	pkt = radclient->cur;

	assert(pkt->reply == NULL);
	pkt->reply = reply;

	/*
	 *	FIXME: Do stuff to process the reply.
	 */
	if (rad_verify(reply,pkt->request, secret) != 0)
	{
		//librad_perror("rad_verify");
		ERROR("rad verify error");
		err = 1;
		goto packet_done; /* shared secret is incorrect */
	}

	if (rad_decode(reply, pkt->request, secret) != 0)
	{
		//librad_perror("rad_decode");
		ERROR("rad decode error");
		err = 2;
		goto packet_done; /* shared secret is incorrect */
	}


packet_done:
	//err = 0;
	recv_statis(radclient, err);

	int reply_code = pkt->reply->code;
	rad_free(&pkt->reply);

	STATE_DEC(radclient->state);

	if(err || reply_code == PW_AUTHENTICATION_REJECT)
	{
#ifdef REJECT_RESENT
		if(reply_code == PW_AUTHENTICATION_REJECT)
			radclient->reject_flag = 1;			//!< for statis sent reject

		if(pkt->retries < retries)
#else
		if(pkt->retries < retries && reply_code != PW_AUTHENTICATION_REJECT) //!<no need resent
#endif
		{
			pkt->timestamp = -1;
			radclient->state = CLIENT_IDLE;
		}
		else
		{
			radclient->cur = radclient->head;
			radclient->state = CLIENT_DEAD;
			CLEAR_PACKET(pkt);
		}
	}
	else
	{
		CLEAR_PACKET(pkt);
		radclient->cur = pkt->next;
		if(radclient->cur == radclient->head)
			radclient->state = CLIENT_DEAD;
		else
			radclient->state = CLIENT_IDLE;
	}

	if(radclient->resend < resend)
		radclient->state = CLIENT_IDLE;


	STATE_INC(radclient->state);

	pthread_mutex_lock(&send_pos_mutex);
	pthread_mutex_lock(&timeout_pos_mutex);	
	//!< if necessary , move this client to send pos
	if(radclient->state == CLIENT_IDLE)
	{
		if(radclient == radclient_head)
			radclient_head = radclient->next;
		
		if(radclient == timeout_pos)
			timeout_pos = radclient->next;

		if(radclient != send_pos)
		{
			radclient = (radclient_t*)remove_from_list((list_t**)(&radclient));
			combine_list((list_t*)send_pos, (list_t*)radclient);
		}
	}
	pthread_mutex_unlock(&timeout_pos_mutex);	
	pthread_mutex_unlock(&send_pos_mutex);

	return 0;
}

/* --------------------------------------------------------------------------*/
/** 
 * @brief hand timeout 
 * 
 * @return 
 * 
 * @review draft
 */
/* ----------------------------------------------------------------------------*/
static int hand_timeout()
{
	radclient_t *radclient;
	package_t *pkt;
	int count = 0;
	radclient_t *start;
	rbnode_t	*node;

	//DBG("hand timeout\n");
	while(get_time() - timeout_pos->cur->timestamp > time_out)
	{
		if(timeout_pos->state == CLIENT_BUSY)
		{
			count ++;
			pkt = timeout_pos->cur;

			
			pthread_mutex_lock(&request_tree_mutex);
			node = rbtree_find(request_tree, timeout_pos);
			if(node)
			{
				rbtree_delete(request_tree, node);
				pthread_mutex_unlock(&request_tree_mutex);
			}
			else
			{

				pthread_mutex_unlock(&request_tree_mutex);
				//dump_request(pkt, stdout);
				ERROR("error find request tree");
				exit(1);
			}

			timeout_statis(timeout_pos, 0);

			STATE_DEC(timeout_pos->state);
			//!< retries not reached
			if(pkt->retries < retries)
			{
				pkt->timestamp = -1;
				timeout_pos->state = CLIENT_IDLE;
			}
			else
			{
				//!< reset this client
				timeout_pos->cur = timeout_pos->head;
				timeout_pos->state = CLIENT_DEAD;
				CLEAR_PACKET(pkt);
			}

			if(timeout_pos->resend < resend)
				timeout_pos->state = CLIENT_IDLE;
			
			
			STATE_INC(timeout_pos->state);

			pthread_mutex_lock(&timeout_pos_mutex);
			pthread_mutex_lock(&send_pos_mutex);
			//!< if necessary , move this client to send pos
			if(timeout_pos->state == CLIENT_IDLE)
			{
				if(timeout_pos == radclient_head)
					radclient_head = timeout_pos->next;
			
				if(timeout_pos != send_pos)
				{
					radclient = (radclient_t*)remove_from_list((list_t**)(&timeout_pos));
					combine_list((list_t*)send_pos, (list_t*)radclient);
				}
			}
			pthread_mutex_unlock(&send_pos_mutex);
			pthread_mutex_unlock(&timeout_pos_mutex);

		}
		else
		{
			pthread_mutex_lock(&timeout_pos_mutex);
			if(timeout_pos->state == CLIENT_DEAD)
				timeout_pos = timeout_pos->next;
			else
			{
				if(timeout_pos != send_pos)
				{
					/*
					node = rbtree_find(request_tree, timeout_pos);
					if(node)
						DBG("find a idle node\n");
					else
						DBG("can't find it\n");
					DBG("timeout_pos:0x%x state:%d next:0x%x state:%d\n", 
							timeout_pos, timeout_pos->state, timeout_pos->next, timeout_pos->next->state);
					DBG("send_pos:0x%x state:%d next:0x%x state:%d\n",
							send_pos, send_pos->state, send_pos->next, send_pos->next->state);
					DBG("client:%d idle:%d busy:%d dead:%d \n", 
						state_count[CLIENT_TOTAL],
						state_count[CLIENT_IDLE],
						state_count[CLIENT_BUSY],
						state_count[CLIENT_DEAD]);
*/
					DBG("IDLE IN TIMEOUT POS");		//< this is a bug need to fix
					timeout_pos = timeout_pos->next;
					//exit(0);
				}
			}
			pthread_mutex_unlock(&timeout_pos_mutex);
		}

		if(timeout_pos == send_pos)
			break;
	}

	return count;

}

/* --------------------------------------------------------------------------*/
/** 
 * @brief initialize the radclint from <fn>
 * 
 * @param fn filename
 * 
 * @return how many client have be created
 * 
 * @review draft
 */
/* ----------------------------------------------------------------------------*/
int load_packages(char *fn)
{
	assert(fn);
	const char	*filename = fn;
	radclient_t	*radclient, *tm;
	int count = 0;

	/*
	 *	Initialize the request we're about
	 *	to send.
	 */
	radclient = radclient_init(filename);
	if (!radclient) {
		exit(1);
	}
	for(tm = radclient; tm->next != radclient; tm = tm->next)
		count ++;
	count ++;
	

	if (!radclient_head)
		radclient_head = radclient;
	else
		combine_list((list_t*)radclient_head->prev, (list_t*)radclient);


	state_count[CLIENT_IDLE] += count;
	state_count[CLIENT_TOTAL] += count;
	return count;
}

void sig_send(int sig)
{
	if(send_pos->state == CLIENT_IDLE)
		send_one_packet(send_pos);
	else
	{
		pthread_mutex_lock(&send_pos_mutex);
		//!< attendtion here, must know send_pos->state have changed or not
		if(send_pos->state != CLIENT_IDLE && send_pos->next->state == CLIENT_IDLE)
			send_pos = send_pos->next;
		pthread_mutex_unlock(&send_pos_mutex);
	}

}
void *send_thread(void *arg)
{
	radclient_t *cur;
	utime_t ti_now = get_time();
	utime_t ti_ori = time_out;
    int count_ori = count_per;
	int ti;


	while(state_count[CLIENT_DEAD] != state_count[CLIENT_TOTAL])
	{
		if(ti_now > get_time())
		{
			//usleep(ti_now - get_time());
			continue;
		}
		ti_now += TIME_GAP / count_per;
	
		//!< this is a good idea
		if(busy_pkt && timeout_decrease)
		{
			if (busy_pkt < state_count[CLIENT_BUSY])
				time_out -= TIME_GAP * timeout_decrease / 1000;	//!< ms
			else
				time_out += TIME_GAP * timeout_decrease / 1000;
			//!< time out < 0
			time_out = (time_out & 0x80000000) ? 0 : (time_out > ti_ori) ? ti_ori : time_out;
		}	

		//!< this also is a good idea
		if(busy_pkt && count_decrease)
		{
			if (busy_pkt < state_count[CLIENT_BUSY])
				count_per -= count_decrease;	//!< 
			else
				count_per += count_decrease;
			//!< time out < 0
            count_per = (count_per > count_ori) ? count_ori : count_per;
		}	
		
		if(send_pos->state == CLIENT_IDLE)
			send_one_packet(send_pos);
		else
		{
			pthread_mutex_lock(&send_pos_mutex);
			//!< attendtion here, must know send_pos->state have changed or not
			if(send_pos->state != CLIENT_IDLE && send_pos->next->state == CLIENT_IDLE)
				send_pos = send_pos->next;
			pthread_mutex_unlock(&send_pos_mutex);
		}
	}


}


void *recv_thread(void *arg)
{
	utime_t t1, t2;
	int time_wait = 0;
	while(state_count[CLIENT_DEAD] != state_count[CLIENT_TOTAL])
	{
		recv_one_packet(time_wait);		//!< this is a fix bug
		hand_timeout();
		//DBG("client:%d idle:%d busy:%d dead:%d \n \ 
		//		send_pos:0x%x state:%d next:0x%x state:%d timeout_pos:0x%x state:%d next:0x%x state:%d\n", 
		//		state_count[CLIENT_TOTAL],
		//		state_count[CLIENT_IDLE],
		//		state_count[CLIENT_BUSY],
		//	state_count[CLIENT_DEAD], 
		//		send_pos, send_pos->state, send_pos->next, send_pos->next->state,
		//		timeout_pos, timeout_pos->state, timeout_pos->next, timeout_pos->next->state);

	}

}

extern char *optarg;
void hand_arg(int argc, char *argv[])
{
	char c;
	while ((c = getopt(argc, argv, ":b:n:t:j:p:r:f:c:shv")) != -1)
	{
		switch(c) 
		{
			case 'n':
				if(!isdigit((int)*optarg))
					usage();
				count_per = atoi(optarg);
				break;

			case 't':
				if (!isdigit((int)*optarg))
					usage();
				time_out = atoi(optarg) * (TIME_GAP / 1000);
				break;

			case 'r':
				if (!isdigit((int)*optarg))
					usage();
				retries = atoi(optarg);
				break;
			
			case 'c':
				if (!isdigit((int)*optarg))
					usage();
				resend = atoi(optarg);
				break;

			case 'j':
				if (!isdigit((int)*optarg))
					usage();
				reject_per = atof(optarg);
				break;

			case 'p':
				if (!isdigit((int)*optarg))
					usage();
				drop_per = atof(optarg);
				break;
			case 'b':
				if (!isdigit((int)*optarg))
					usage();
				busy_pkt = atoi(optarg);
				break;
			case 'w':
				session_control = 1;	
				break;
            case 'k':
				if (!isdigit((int)*optarg))
					usage();
				timeout_decrease = atoi(optarg);
				break;
            case 'd':
				if (!isdigit((int)*optarg))
					usage();
				count_decrease = atoi(optarg);
				break;



			default:
				usage();
				break;
		}
	}

    if(busy_pkt && !timeout_decrease && !count_decrease)
    {
        printf("if -b is set, -k or -d should be set too\n");
        usage();
    }

	argc -= (optind - 1);
	argv += (optind - 1);
    
#ifdef CLIENT
	if(argc < 2)
		usage();
	server_ipaddr = ip_getaddr(argv[1]);
#endif

}

int main(int argc, char **argv)
{
	const char *radius_dir = RADDBDIR;
#ifdef SERVER
	pthread_t server;
#endif 
#ifdef CLIENT
	struct itimerval iti;
	pthread_t send, recv;
#endif

	hand_arg(argc, argv);

#ifdef CLIENT
	//!< create reqeust tree
	request_tree = rbtree_create(request_cmp, radclient_free, 0);
	if (!request_tree) {
		fprintf(stderr, "radclient: Out of memory\n");
		exit(1);
	}

#endif
	//!< init dict
	if (dict_init("dic", RADIUS_DICTIONARY) < 0)
	{
		librad_perror("radclient");
		return 1;
	}
	if(pthread_mutex_init(&resource_mutex, NULL))
	{
		DBG("main resource_mutex init error\n");
		exit(0);
	}


#ifdef CLIENT
	//!< read data
	printf("%d\n", load_packages("QueryFile"));
	send_pos = timeout_pos = radclient_head;
	pthread_mutex_init(&send_pos_mutex, NULL);
	pthread_mutex_init(&timeout_pos_mutex, NULL);
#endif

#ifdef SERVER

	//!< create server
	if(pthread_cond_init(&server_done, NULL))
	{
		DBG("main server done init error\n");
		exit(0);
	}
	if(pthread_mutex_init(&server_mutex, NULL))
	{
		pthread_cond_destroy(&server_done);
		DBG("main server_mutex init error\n");
		exit(0);
	}
	DBG("wait for server done\n");
	pthread_mutex_lock(&server_mutex);
	pthread_create(&server, NULL, radserver, NULL);			//!< must be create here
	pthread_cond_wait(&server_done, &server_mutex);
	pthread_mutex_unlock(&server_mutex);

	DBG("server done\n");
#endif

	//!< output
	DBG("output init");
	output_init(1);	
	DBG("done\n");
	utime_init();

#ifdef CLIENT
	
	statis_start = get_time();
	pthread_mutex_init(&state_mutex, NULL);
	//!< create recv thread
	//
	DBG("create recv thread .........");
	if ((pthread_create(&recv, NULL, recv_thread, NULL)))
	{
		ERROR("recv thread create error\n");
		exit(0);
	}
	DBG("done");

	//!< create send thread
	DBG("create send thread .........");
	if ((pthread_create(&send, NULL, send_thread, NULL)))
	{
		ERROR("send thread create error\n");
		exit(0);
	}
	DBG("done");

	pthread_join(recv, NULL);
	DBG("recv thread finished\n");

	pthread_join(send, NULL);
	DBG("send thread finished\n");

	
	pthread_mutex_destroy(&state_mutex);
	pthread_mutex_destroy(&resource_mutex);
	pthread_mutex_destroy(&send_pos_mutex);
	pthread_mutex_destroy(&timeout_pos_mutex);	
//!< free sockfd pool
	free_resource();

#endif

#ifdef SERVER
#ifdef CLIENT
	pthread_cancel(server);
#else
	pthread_join(server, NULL);
#endif
#endif

	output_close();
#ifdef SERVER
	pthread_cond_destroy(&server_done);
	pthread_mutex_destroy(&server_mutex);
#endif

	return;



}

